package rbm.utiles;

/**
   The class that actually generates new puzzles. Create an instance
   with a new EditablePuzzleDef as parameter, this specifies dimensions
   of the puzzle and the block shape. From that the puzzle can be
   generated.
*/


import javax.swing.*;


import rbm.modelado.datos.*;
import rbm.modelado.global.interfaces.PuzzleConstants;


class Generator extends Thread implements PuzzleConstants
{

   private EditablePuzzleDef pd;
   private JTextArea tArea;

   private boolean[][] board;
   private String methodType;
   private int numBlocks;
   private int nodesWeight;
   private int lengthWeight;
   private int branchWeight;
   private int blocksWeight;
   private int turnsWeight;
   private int loopWeight;
   private int muteRate;
   private boolean fixStartPos;

   private int bestX = -1;
   private int bestY = -1;


	// Set to accept best solution so far
   private boolean stopFlag;
   private Editor edit;

int totalBestScore = 0;

	// Gets puzzle to generate on, and text area to write
	// progress reports to

   public Generator( EditablePuzzleDef e, EditorPanel panel, String t, boolean fix,
      int num, int node, int length, int branch, int blocks, int turns, int loops, Editor ed )
   {
      pd = e;
      ep = panel;
      methodType = t;
      fixStartPos = fix;
      numBlocks = num;
      nodesWeight = node;
      lengthWeight = length;
      branchWeight = branch;
      blocksWeight = blocks;
      turnsWeight = turns;
      loopWeight = loops;

      edit = ed;
      tArea = ed.getTextArea();

      board = pd.getBoard();

      if( t != PuzzleConstants.ITERATIVE )
      {
         System.err.println( "Wrong number of arguments to generator: may not funtion correctly." );
      }
   }

   public Generator( EditablePuzzleDef e, EditorPanel panel, String t, boolean fix,
      int num, int node, int length, int branch, int blocks, int turns, int loops,
         int mute, Editor ed )
   {
      pd = e;
      ep = panel;
      methodType = t;
      fixStartPos = fix;
      numBlocks = num;
      nodesWeight = node;
      lengthWeight = length;
      branchWeight = branch;
      blocksWeight = blocks;
      turnsWeight = turns;
      loopWeight = loops;
      muteRate = mute;

      edit = ed;
      tArea = ed.getTextArea();

      board = pd.getBoard();

      if( t != PuzzleConstants.RANDOM )
      {
         System.err.println( "Wrong number of arguments to generator: may not funtion correctly." );
      }
   }


   public void run()
   {
      stopFlag = false;

      if( methodType == PuzzleConstants.ITERATIVE )
         iterativeSearch();
      else if( methodType == PuzzleConstants.RANDOM )
         randomMutate();

      edit.generationFinished();
   }



   private void randomMutate()
   {

      totalBestScore = 0;

      tArea.setText( "Starting...\n" );

      addRandomBlocks();


      while( !stopFlag )
      {

	// Copy locally for convenience, reference changes each iteration
         board = pd.getBoard();


	// Copy board

         boolean[][] oldBoard = new boolean[board.length][board[0].length];
         for( int x = 0; x < board.length; x++ )
            for( int y = 0; y < board[0].length; y++ )
               oldBoard[x][y] = board[x][y];



	// Copy old starting position

         int[] oldStart = pd.getStartCoord();
         int oldX = oldStart[0];
         int oldY = oldStart[1];


	// Randomize some blocks

         for( int i = 0; i < board.length; i++ )
            for( int j = 0; j < board[0].length; j++ )
               if( Math.random() * 1000 < muteRate )
                  pd.toggleTile( i, j );


	// Set bestX, bestY, bestScore
         doBestPositions();


	// If no worse than last time, keep it, otherwise
	// revert to previous

         if( bestScore >= totalBestScore )
         {

            if( bestScore > totalBestScore )
               tArea.append( "\nScore " + bestScore );

            totalBestScore = bestScore;

            pd.setStartCoord( bestX, bestY );

            Solver s = new Solver( pd );
            SolucionRBMImpl sol = s.solve();
            if(sol != null)

		// Remove all finish squares, set to the new finish position

            pd.clearFinishCoords();
            boolean[][][] finShape = sol.getLongestPathNode().getShape();

            for( int i = 0; i < finShape.length; i++ )
               for( int j = 0; j < finShape[0].length; j++ )
                  if( finShape[i][j][0] )
                     pd.toggleFinishCoord( i + sol.getLongX(), j + sol.getLongY() );

            ep.repaint();
         }
         else
         {
		// Restore board, copy into a new array, then send that to
		// pd, or else board = oldBoard in subsequent iterations.
		// Create replacementBoard, a new array each time

            boolean[][] replacementBoard = new boolean[oldBoard.length][oldBoard[0].length];

            for( int i = 0; i < oldBoard.length; i++ )
               for( int j = 0; j < oldBoard[0].length; j++ )
                  replacementBoard[i][j] = oldBoard[i][j];

            pd.setBoard( replacementBoard );
            pd.setStartCoord( oldX, oldY );
         }

      }

      ep.repaint();
   }



   private void addRandomBlocks()
   {
      for( int i = 0; i < numBlocks; i++ )
      {
         int x = (int)( Math.random() * board.length );
         int y = (int)( Math.random() * board[0].length );
         pd.toggleTile( x, y );
         ep.repaint();
      }
   }



   private void iterativeSearch()
   {

      board = pd.getBoard();

	// Start with some 'seed' blocks
      addRandomBlocks();

      boolean loop;
      int totalBestScore = 0;
      int finalStartX = 0;
      int finalStartY = 0;

      tArea.setText( "Starting...\n" );

      do
      {

         loop = false;

	// for each poss place, add a block

         bestX = -1;
         bestY = -1;
         bestScore = 0;

         int bestBlockX = -1;
         int bestBlockY = -1;


         for( int i = 0; i < board.length; i++ )
         {

            for( int j = 0; j < board[0].length; j++ )
            {
               System.out.println( "Testing "+ i +" "+ j );
               pd.toggleTile( i, j );

               doBestPositions();

               if( bestScore > totalBestScore )
               {
                  totalBestScore = bestScore;
                  bestBlockX = i;
                  bestBlockY = j;
                  finalStartX = bestX;
                  finalStartY = bestY; 
                  pd.setStartCoord( bestX, bestY );
                  loop = true;

                  Solver s = new Solver( pd );
                  SolucionRBMImpl sol = s.solve();

                  System.out.println( bestX+" "+bestY+" "+bestScore );

                  pd.clearFinishCoords();
                  boolean[][][] finShape = sol.getLongestPathNode().getShape();

                  for( int m = 0; m < finShape.length; m++ )
                     for( int n = 0; n < finShape[0].length; n++ )
                        if( finShape[m][n][0] )
                           pd.toggleFinishCoord( m + sol.getLongX(), n + sol.getLongY() );

                  ep.repaint();
               }

               pd.toggleTile( i, j );	// set back to same as before

               if( stopFlag )
                  break;
            }

            if( stopFlag )
               break;
         }


         if( loop )
            pd.toggleTile( bestBlockX, bestBlockY );


         pd.setStartCoord( finalStartX, finalStartY );

         ep.repaint();

         if( bestBlockX != -1 )
         {
            System.out.println("looping..., changed " +bestBlockX +" "+ bestBlockY);
            tArea.append( "\nChanged " + bestBlockX + ", " + bestBlockY
               + "\nScore " + totalBestScore );
         }

      }
      while( loop && !stopFlag );

      System.out.println( "Finished" );
      tArea.append( "\nFinished" );


   }



	// Solves puzzle for every start coord,
	// sets bestX and bestY as the best start coord

   private void doBestPositions()
   {

      bestScore = 0;

	// Don't loop through all start possibilities

      if( fixStartPos )
      {
         bestX = pd.getStartCoord()[0];
         bestY = pd.getStartCoord()[1];


         if( !checkValidStartPos( bestX, bestY ) )
            return;

		// get a solution
         Solver s = new Solver( pd );
         SolucionRBMImpl sol = s.solve();

		// score this solution
         int newScore = doScore( sol );

         if( newScore >= bestScore )
            bestScore = newScore;

         return;
      }


	// Else do loop through all

      bestX = -1;
      bestY = -1;

      for( int i = 0; i < board.length; i++ )
         for( int j = 0; j < board[0].length; j++ )
         {
            pd.setStartCoord( i, j );

            if( !checkValidStartPos( i, j ) )
               continue;

		// get a solution
            Solver s = new Solver( pd );
            SolucionRBMImpl sol = s.solve();

		// score this solution
            int newScore = doScore( sol );

            if( newScore >= bestScore )
            {
               bestX = i;
               bestY = j;
               bestScore = newScore;
            }
         }
   }


	// Checks if block starts inside grid, not overlapping
	// impassable squares

   private boolean checkValidStartPos( int i, int j )
   {
      boolean[][] footprint = Block.getFootprint( pd.getBlockShape() );

      if( i + footprint.length > board.length ||
         j + footprint[0].length > board[0].length )
            return false;

      for( int x = 0; x < footprint.length; x++ )
         for( int y = 0; y < footprint[0].length; y++ )
            if( footprint[x][y] && pd.getBoard()[x+i][y+j] )
               return false;

      return true;
   }



	// Gives a Solution a score, should be higher for
	// 'better' puzzles
	//
	// Number of nodes affects a bit, if too high a ratio will
	// just get empty board.
	// Minus number of blocks, looks better with less blocks

   private int doScore( SolucionRBMImpl sol )
   {
      int score = 0;

      score += sol.getNumNodes() * nodesWeight;

      score += sol.getLongestPathNode().getMinMoves() * lengthWeight;

      score += sol.getLongestPathNode().getNumBranches() * branchWeight;

      score += countBlocks() * blocksWeight;

      score += turnsWeight * sol.getNumDirectionChanges();

      int av=0;
      if( sol.getNumLoops() > 0 )
      {
         av = sol.getTotalLoopLength() / sol.getNumLoops();
         score +=  loopWeight * av;

      }


      if(score > totalBestScore)
      {
         System.out.println("Num of Nodes "+ (sol.getNumNodes() * nodesWeight));
         System.out.println("Path Length "+ (sol.getLongestPathNode().getMinMoves() * lengthWeight));
         System.out.println("Num of Branches "+(sol.getLongestPathNode().getNumBranches() * branchWeight));
         System.out.println("Num of Blocks "+(countBlocks() * blocksWeight));
         System.out.println("Direction changes "+ turnsWeight * sol.getNumDirectionChanges());
         System.out.println("Loop average "+ loopWeight * av);

         System.out.println();
      }

      return score;

   }



   private int countBlocks()
   {
      int c = 0;
      for( int i = 0; i < board.length; i++ )
         for( int j = 0; j < board[0].length; j++ )
            if( board[i][j] )
               c++;

      return c;
   }


   public void stopGenerating()
   {
      stopFlag = true;
   }

}